package com.niiwoo.civet.trade.service.local.newRefund.refundDeal;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.fastjson.JSON;
import com.google.common.base.Joiner;
import com.niiwoo.civet.account.constant.MQConstant;
import com.niiwoo.civet.account.dto.request.TransFreezeRequestDTO;
import com.niiwoo.civet.account.dto.response.AccountBaseResponse;
import com.niiwoo.civet.account.service.AccountDubboService;
import com.niiwoo.civet.base.enums.BorrowProjectTypeEnum;
import com.niiwoo.civet.base.enums.OrderTradeSourceEnum;
import com.niiwoo.civet.base.enums.RefundTypeEnum;
import com.niiwoo.civet.trade.constant.TradeMqConstant;
import com.niiwoo.civet.trade.dao.entity.*;
import com.niiwoo.civet.trade.dao.mapper.*;
import com.niiwoo.civet.trade.dto.newRefund.ReceiveFeeDTO;
import com.niiwoo.civet.trade.dto.newRefund.RefundFeeDTO;
import com.niiwoo.civet.trade.dto.newRefund.RefundStateDTO;
import com.niiwoo.civet.trade.enums.*;
import com.niiwoo.civet.trade.enums.creditorSale.SaleDetailEndTypeEnum;
import com.niiwoo.civet.trade.enums.creditorSale.SaleDetailStatusEnum;
import com.niiwoo.civet.trade.service.local.creditor.CreditorSaleOrderCancelService;
import com.niiwoo.civet.trade.service.local.newRefund.common.RefundCommonService;
import com.niiwoo.civet.trade.service.local.newRefund.notify.dto.RefundNotifyMsgDTO;
import com.niiwoo.civet.trade.service.local.newRefund.notify.enums.RefundNotifyRefundStatusEnum;
import com.niiwoo.civet.trade.service.local.newRefund.refundDeal.core.FeeCalcForReceiveService;
import com.niiwoo.civet.trade.service.local.newRefund.refundDeal.core.FeeCalcForRefundService;
import com.niiwoo.civet.trade.service.local.newRefund.refundDeal.core.FreezeDealingService;
import com.niiwoo.civet.trade.service.local.newRefund.refundDeal.core.RefundService;
import com.niiwoo.civet.trade.service.local.refund.CommonLocalService;
import com.niiwoo.tripod.provider.exception.BizException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.joda.time.DateTime;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.*;

/**
 *  机构代还
 */
@Service
@Slf4j
public class RefundForRepaymentService {

    @Autowired
    private ProjectMapperExt projectMapperExt;

    @Autowired
    private ProjectBorrowerSummaryMapperExt projectBorrowerSummaryMapperExt;

    @Autowired
    private RefundService refundService;

    @Autowired
    private FeeCalcForRefundService feeCalcForRefundService;

    @Reference(version = "1.0.0")
    private AccountDubboService accountDubboService;

    @Autowired
    private FreezeDealingService freezeDealingService;

    @Autowired
    private FeeCalcForReceiveService feeCalcForReceiveService;
    
    @Autowired
    private ProjectBorrowerPlanNewMapperExt projectBorrowerPlanNewMapperExt;

    @Autowired
    private RefundCommonService refundCommonService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private ProjectInvestOrderMapperExt projectInvestOrderMapperExt;

    @Autowired
    private InvestorCreditorMapperExt investorCreditorMapperExt;

    @Autowired
    private CreditorSaleDetailMapperExt creditorSaleDetailMapperExt;

    @Autowired
    private CommonLocalService commonLocalService;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private CreditorSaleOrderCancelService creditorSaleOrderCancelService;
    @Autowired
    private ProjectInvestorPlanMapperExt projectInvestorPlanMapperExt;
    @Autowired
    private ProjectPackageInvestOrderMapperExt projectPackageInvestOrderMapperExt;
    @Autowired
    private ProjectPackageInvestOrderDetailMapperExt projectPackageInvestOrderDetailMapperExt;

    private static int REFUND_WAIT_SECONDS = 10;


    /**
     * 借款用户还款(按状态还款)
     * @param projectId
     * @param orderTradeSourceEnum
     * @param refundTypeEnum
     * @param currentDate
     * @param orgRequestNo
     * @return
     */
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public RefundOrder refundForRepayment(long projectId, OrderTradeSourceEnum orderTradeSourceEnum, RefundTypeEnum refundTypeEnum, Date currentDate,String orgRequestNo){
        Project project = projectMapperExt.selectByPrimaryKey(projectId);
        Long sourceAccountId = project.getBorrowAccountId();
        String sourcePlatformUserNo = project.getBorrowPlatformUserNo();
        String sourceUserId = project.getBorrowUserId();

        RefundOptTypeEnum refundOptTypeEnum = RefundOptTypeEnum.getRealRefundOptTypeEnum(RefundOptTypeEnum.BY_PROJECT_STATS, refundTypeEnum);
        return this.refund(new RefundParam(project, orderTradeSourceEnum, refundTypeEnum, RefundTradeTypeEnum.REPAYMENT,
                currentDate, sourceAccountId, sourcePlatformUserNo, sourceUserId, refundOptTypeEnum, currentDate,null,orgRequestNo));
    }

    /**
     * 借款用户还款(按状态还款JOB,手动模拟)
     * @param projectId
     * @param orderTradeSourceEnum
     * @param currentDate
     * @return
     */
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public RefundOrder refundForRepaymentForJob(long projectId, OrderTradeSourceEnum orderTradeSourceEnum, Date currentDate){
        Project project = projectMapperExt.selectByPrimaryKey(projectId);
        Long sourceAccountId = project.getBorrowAccountId();
        String sourcePlatformUserNo = project.getBorrowPlatformUserNo();
        String sourceUserId = project.getBorrowUserId();
        Date compareDate = currentDate;
        return this.refund(new RefundParam(project, orderTradeSourceEnum, RefundTypeEnum.IMMEDIATELY, RefundTradeTypeEnum.REPAYMENT, currentDate,
                sourceAccountId, sourcePlatformUserNo, sourceUserId, RefundOptTypeEnum.BY_COMPARE_DATE, compareDate,null,null));
    }

    /**
     * 借款用户还款（按状态还款JOB）
     * @param userId
     * @param orderTradeSourceEnum
     * @param currentDate
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void refundForRepaymentForJob(String userId, OrderTradeSourceEnum orderTradeSourceEnum, Date currentDate){
        String currentDateStr = commonLocalService.day2String(currentDate);
        List<Long> projectIds = projectBorrowerSummaryMapperExt.listUserRepayProjectId(userId, currentDateStr);
        BigDecimal availableAmount = null;
        RefundForRepaymentService refundForRepaymentService = (RefundForRepaymentService) AopContext.currentProxy();
        for (Long projectId : projectIds) {
            try {
                log.info("job还款，标的projectId={},currentDate={}", projectId, currentDateStr);
                Project project = projectMapperExt.selectByPrimaryKey(projectId);
                if("kaolalicai".equalsIgnoreCase(project.getChannelCode())){
                    log.info("此还款流程不适用kaolalicai标的，标的projectId={},currentDate={}", projectId, currentDateStr);
                    continue;
                }
                Long sourceAccountId = project.getBorrowAccountId();
                if(null == availableAmount){
                    AccountBaseResponse accountBaseResponse = accountDubboService.loadAccountById(sourceAccountId);
                    availableAmount = accountBaseResponse.getAvailableAmount();
                }
                String sourcePlatformUserNo = project.getBorrowPlatformUserNo();
                String sourceUserId = project.getBorrowUserId();
                Date compareDate = currentDate;
                RefundOrder refundOrder = refundForRepaymentService.refund(new RefundParam(project, orderTradeSourceEnum, RefundTypeEnum.IMMEDIATELY, RefundTradeTypeEnum.REPAYMENT, currentDate,
                        sourceAccountId, sourcePlatformUserNo, sourceUserId, RefundOptTypeEnum.BY_COMPARE_DATE, compareDate, null, null, availableAmount));
                availableAmount = availableAmount.subtract(refundOrder.getAmount());
            } catch (Exception e) {
                log.error("job还款Exception，标的projectId={},currentDate={}", projectId, currentDateStr, e);
            }
        }

    }

    /**
     * 代还
     * @param projectId
     * @param orderTradeSourceEnum
     * @param currentDate
     * @param refundTypeEnum
     * @param sourceAccountId
     * @param sourcePlatformUserNo
     * @param sourceUserId
     * @return
     */
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public RefundOrder refundForReplace(long projectId, OrderTradeSourceEnum orderTradeSourceEnum, Date currentDate,
                                        RefundTypeEnum refundTypeEnum, Long sourceAccountId, String sourcePlatformUserNo,
                                        String sourceUserId){
        Project project = projectMapperExt.selectByPrimaryKey(projectId);
        RefundOptTypeEnum refundOptTypeEnum = RefundOptTypeEnum.getRealRefundOptTypeEnum(RefundOptTypeEnum.BY_PROJECT_STATS, refundTypeEnum);
        Date compareDate = currentDate;
        return this.refund(new RefundParam(project, orderTradeSourceEnum, refundTypeEnum, RefundTradeTypeEnum.REPLACE,
                currentDate, sourceAccountId, sourcePlatformUserNo, sourceUserId, refundOptTypeEnum, compareDate,null,null));
    }

    /**
     * 指定期数还款 (第三方还款，r360,卡牛等)
     * @param projectId
     * @param orderTradeSourceEnum
     * @param refundTypeEnum
     * @param currentDate
     * @param periods
     * @return
     */
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public RefundOrder refundByPeriods(long projectId, OrderTradeSourceEnum orderTradeSourceEnum,
                                       RefundTypeEnum refundTypeEnum, Date currentDate,List<Integer> periods,String orgRequestNo){
        Boolean isLegal = this.checkPeriodsIsLegal(projectId,periods);
        if(!isLegal){
            throw new BizException("TRD_800015");
        }
        Project project = projectMapperExt.selectByPrimaryKey(projectId);
        Long sourceAccountId = project.getBorrowAccountId();
        String sourcePlatformUserNo = project.getBorrowPlatformUserNo();
        String sourceUserId = project.getBorrowUserId();

        RefundOptTypeEnum refundOptTypeEnum = RefundOptTypeEnum.getRealRefundOptTypeEnum(RefundOptTypeEnum.BY_SPECIFIED_PERIODS, refundTypeEnum);
        return this.refund(new RefundParam(project, orderTradeSourceEnum, refundTypeEnum, RefundTradeTypeEnum.REPAYMENT,
                currentDate, sourceAccountId, sourcePlatformUserNo, sourceUserId, refundOptTypeEnum, currentDate, periods, orgRequestNo));
    }

    /**
     * 公共还款逻辑处理
     * orgRequestNo
     * @param refundParam
     * @return
     */
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public RefundOrder refund(RefundParam refundParam){
        Project project = refundParam.getProject();
        OrderTradeSourceEnum orderTradeSourceEnum = refundParam.getOrderTradeSourceEnum();
        RefundTypeEnum refundTypeEnum = refundParam.getRefundTypeEnum();
        RefundTradeTypeEnum refundTradeTypeEnum = refundParam.getRefundTradeTypeEnum();
        Date currentDate = refundParam.getCurrentDate();
        Long sourceAccountId = refundParam.getSourceAccountId();
        String sourcePlatformUserNo = refundParam.getSourcePlatformUserNo();
        String sourceUserId = refundParam.getSourceUserId();
        RefundOptTypeEnum refundOptTypeEnum = refundParam.getRefundOptTypeEnum();
        Date compareDate = refundParam.getCompareDate();
        List<Integer> periods = refundParam.getPeriods();
        String orgRequestNo = refundParam.getOrgRequestNo();
        BigDecimal availableAmount = refundParam.getAvailableAmount();
        Long projectId = project.getProjectId();
        if(BorrowProjectTypeEnum.DECORATION_LOAN.getValue().equals(project.getType())){
            log.error("装修借当前不需要还款，projectId=" + projectId);
            throw new BizException("TRD_800019");
        }
        ProjectBorrowerSummary summary = projectBorrowerSummaryMapperExt.selectByPrimaryKeyForUpdate(projectId);

        if(refundCommonService.isCalculateInterestByDay(project.getOrgId(), project.getDeadlineUnit(), summary.getRefundDate(), currentDate)){
            refundTypeEnum = RefundTypeEnum.OVERDEPT;
        }

        try{
            //还款准入检查
            refundService.check(summary, refundTypeEnum, currentDate, project.getDeadlineUnit());
            if(RefundTradeTypeEnum.REPLACE.equals(refundTradeTypeEnum)){
                // 查看该标的是否有被其他机构垫付
                if(Byte.valueOf("1").equals(summary.getIsAdvanced())){
                    throw new BizException("TRD90013");
                }
            }
            // 获取应还金额
            List<RefundFeeDTO> refundFeeDTOS = feeCalcForRefundService.caculateAmount(project, summary, refundTypeEnum, refundOptTypeEnum, compareDate, currentDate,periods, project.getDeadlineUnit());
            RefundStateDTO refundStateDTO = feeCalcForRefundService.convertToRefundFee(refundFeeDTOS);
            if(refundStateDTO.getAmount().compareTo(BigDecimal.ZERO) <= 0){
                log.error("当前不需要还款，projectId=" + projectId);
                throw new BizException("TRD_800020");
            }

            // 待还和待收检查
            Set<Integer> periodSet = new HashSet<>();
            for (RefundFeeDTO refundFeeDTO : refundFeeDTOS) {
                periodSet.add(refundFeeDTO.getPeriod());
            }
            List<ReceiveFeeDTO> receiveFeeDTOList = feeCalcForReceiveService.getReceiveFeeDTOList(project, summary, new ArrayList<>(periodSet), currentDate, refundTypeEnum, project.getDeadlineUnit());
            BigDecimal capital = BigDecimal.ZERO;
            BigDecimal interest = BigDecimal.ZERO;
            BigDecimal penaltyFee = BigDecimal.ZERO;
            for (ReceiveFeeDTO receiveFeeDTO : receiveFeeDTOList) {
                capital = capital.add(receiveFeeDTO.getCapital());
                interest = interest.add(receiveFeeDTO.getInterest());
                penaltyFee = penaltyFee.add(receiveFeeDTO.getPenaltyFee());
            }
            if(refundStateDTO.getCapital().compareTo(capital) != 0
                    || refundStateDTO.getInterest().compareTo(interest) != 0
                    || refundStateDTO.getPenaltyFee().compareTo(penaltyFee) != 0){
                log.error("标的还款失败，待还和待收金额不匹配，projectId=" + projectId);
                throw new RuntimeException("标的还款失败，待还和待收金额不匹配，projectId=" + projectId);
            }

            RefundStateDTO couldRefundStateDTO;
            if(RefundTradeTypeEnum.REPAYMENT.equals(refundTradeTypeEnum)){
                // 获取账户余额
                if(null == availableAmount){
                    AccountBaseResponse accountBaseResponse = accountDubboService.loadAccountByIdFromMaster(project.getBorrowAccountId());
                    availableAmount = accountBaseResponse.getAvailableAmount();
                }
                // 获取可还金额
                List<RefundFeeDTO> refundFeeDTOList = feeCalcForRefundService.getRefundFeeDTOList(refundFeeDTOS, availableAmount);
                couldRefundStateDTO = feeCalcForRefundService.convertToRefundFee(refundFeeDTOList);
            } else if(RefundTradeTypeEnum.REPLACE.equals(refundTradeTypeEnum)){
                couldRefundStateDTO = refundStateDTO;
            } else{
                throw new RuntimeException("还款类型：tradeType设置有误");
            }
            if(refundStateDTO.getAmount().compareTo(couldRefundStateDTO.getAmount()) == 0){
                log.info("执行足额还款，projectId=" + projectId);
            }else{
                boolean needEnough = false;
                if(RefundTypeEnum.OVERDEPT.equals(refundTypeEnum)){
                    needEnough = true;
                }else if(RepayStatusEnum.NORMAL.getCode().equals(summary.getRepayStatus())){
                    needEnough = orderTradeSourceEnum.isNeedEnough();
                }else if(OrderTradeSourceEnum.BALANCE_FULL_JOB.getTradeSource().equals(orderTradeSourceEnum.getTradeSource())){
                    needEnough = true;
                }
                if(needEnough){
                    log.error("您的余额不足，无法完成本次还款,refundStateDTO={}", JSON.toJSONString(refundStateDTO));
                    throw new BizException("TRD90012");
                }else{
                    if(couldRefundStateDTO.getAmount().compareTo(BigDecimal.valueOf(10)) < 0){
                        log.error("标的还款失败，可用余额低于10元，projectId=" + projectId);
                        throw new BizException("TRD_800003");// 对不起，您的账户可用余额低于10元，请先进行充值
                    }else{
                        log.info("执行非足额还款，projectId=" + projectId);
                        if(OrderTradeSourceEnum.BALANCE_PARTIAL_JOB.equals(orderTradeSourceEnum)
                                && RefundTradeTypeEnum.REPAYMENT.equals(refundTradeTypeEnum)){
                            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                                @Override
                                public void afterCompletion(int status) {
                                    super.afterCompletion(status);
                                    // 进入逾期
                                    RefundForRepaymentService refundForRepaymentService = (RefundForRepaymentService) AopContext.currentProxy();
                                    refundForRepaymentService.projectOverdueHandle(summary, refundParam, null);
                                }
                            });
                        }
                        if (OrderTradeSourceEnum.BALANCE_PARTIAL_JOB.equals(orderTradeSourceEnum)
                                && (RepayStatusEnum.OVERDUE.getCode().equals(summary.getRepayStatus()) || RepayStatusEnum.BADLOANS.getCode().equals(summary.getRepayStatus()))) {
                            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                                @Override
                                public void afterCompletion(int status) {
                                    super.afterCompletion(status);
                                    // 投资订单逾期处理 -投资订单状态维护（已逾期标的，投资订单在垫付后会变为正常回款中，后续订单如果逾期，需在此处处理）
                                    RefundForRepaymentService refundForRepaymentService = (RefundForRepaymentService) AopContext.currentProxy();
                                    refundForRepaymentService.fixOverdueForInvestOrders(summary, refundParam);
                                }
                            });
                        }
                    }
                }
            }
            RefundOrder refundOrder = refundService.createRefundOrder(refundTypeEnum, refundTradeTypeEnum,  orderTradeSourceEnum, refundStateDTO, couldRefundStateDTO,
                    project, sourceAccountId, sourcePlatformUserNo, sourceUserId, currentDate, summary.getRepayStatus(), compareDate, null,
                    refundOptTypeEnum, periods==null?null:Joiner.on(",").skipNulls().join(periods),orgRequestNo);


            TransFreezeRequestDTO transFreezeRequestDTO = freezeDealingService.buidTransFreezeRequestDTO(refundOrder, project.getType(), project.getTitle(),
                    summary.getCurrentPeriod(), summary.getTotalPeriod());
            //非正常还款 和 JOB发起的还款 ，直接发送到账户中心
            if (!project.getSubStatus().equals(ProjectSubStatusEnum.REPAYING_INIT.getSubStatus())
                    || OrderTradeSourceEnum.BALANCE_FULL_JOB.equals(orderTradeSourceEnum)
                    || OrderTradeSourceEnum.BALANCE_PARTIAL_JOB.equals(orderTradeSourceEnum)
                    || OrderTradeSourceEnum.GUARANTEE_JOB.equals(orderTradeSourceEnum)){
                // JOB还款，直接发起资金冻结请求
                rabbitTemplate.convertAndSend(MQConstant.REFUND_FREEZE_EXCHANGE, MQConstant.REFUND_FREEZE_ROUTING, transFreezeRequestDTO);
            }else {
                // 非JOB，需走债权校验
                rabbitTemplate.convertAndSend(MQConstant.REFUND_VALIDATE_EXCHANGE, MQConstant.REFUND_VALIDATE_ROUTING, transFreezeRequestDTO);
            }
            return refundOrder;
        }catch(Exception e) {
            log.error("还款or垫付创建订单失败：refundParam={}", JSON.toJSONString(refundParam), e);
            RefundForRepaymentService refundForRepaymentService = (RefundForRepaymentService) AopContext.currentProxy();
            if (e instanceof BizException) {
                BizException bizException = (BizException) e;
                if ("TRD90012".equals(bizException.getErrorCode()) || "TRD_800003".equals(bizException.getErrorCode())) {

                    if (OrderTradeSourceEnum.BALANCE_PARTIAL_JOB.equals(orderTradeSourceEnum)
                            && (RepayStatusEnum.OVERDUE.getCode().equals(summary.getRepayStatus()) || RepayStatusEnum.BADLOANS.getCode().equals(summary.getRepayStatus()))){
                        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                            @Override
                            public void afterCompletion(int status) {
                                super.afterCompletion(status);
                                // 投资订单逾期处理 -投资订单状态维护（已逾期标的，投资订单在垫付后会变为正常回款中，后续订单如果逾期，需在此处处理）
                                RefundForRepaymentService refundForRepaymentService = (RefundForRepaymentService) AopContext.currentProxy();
                                refundForRepaymentService.fixOverdueForInvestOrders(summary, refundParam);
                            }
                        });
                    }

                    if(OrderTradeSourceEnum.BALANCE_PARTIAL_JOB.equals(orderTradeSourceEnum)
                            && RefundTradeTypeEnum.REPAYMENT.equals(refundTradeTypeEnum)){
                        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                            @Override
                            public void afterCompletion(int status) {
                                super.afterCompletion(status);
                                // 进入逾期
                                refundForRepaymentService.projectOverdueHandle(summary, refundParam, bizException.getMessage());
                            }
                        });
                    } else {
                        refundForRepaymentService.refundExceptionNotify(refundParam, bizException.getMessage());
                    }
                }
            } else {
                refundForRepaymentService.refundExceptionNotify(refundParam, "还款失败");
            }
            throw e;
        }
    }

    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void freezeWithValidateDebt(TransFreezeRequestDTO transFreezeRequestDTO) {
        if (new DateTime(transFreezeRequestDTO.getRefundOrderCreateTime()).plusSeconds(REFUND_WAIT_SECONDS).isAfter(new DateTime())){
            //该处用于处理还款订单事物未提交，导致债权校验绕过，之后债权订单生成但事物未提交时，进入此方法
            throw new BizException("标的等待后再进行还款处理,等待时间(秒):"+REFUND_WAIT_SECONDS);
        }
        Long start = new Date().getTime();
        log.info("selectInProgressOrderByProjectId-start:{}",start);
        List<CreditorSaleDetail> creditorSaleDetailList = creditorSaleDetailMapperExt.selectInProgressOrderByProjectId(transFreezeRequestDTO.getProjectId());
        log.info("selectInProgressOrderByProjectId-end:{},用时={}毫秒",new Date().getTime(),new Date().getTime()-start);

        List<CreditorSaleDetail> creditorSaleDetailListSelling = new ArrayList<>();

        for (CreditorSaleDetail creditorSaleDetail : creditorSaleDetailList) {
            if (SaleDetailStatusEnum.SELLING.getValue().equals(creditorSaleDetail.getStatus())
                    || SaleDetailStatusEnum.PRE_CREDIT_ASSIGNMENT_HANDING.getValue().equals(creditorSaleDetail.getStatus())
                    || SaleDetailStatusEnum.CONFIRM_CREDIT_ASSIGNMENT_HANDING.getValue().equals(creditorSaleDetail.getStatus())
                    || SaleDetailStatusEnum.SALE_APPLYING.getValue().equals(creditorSaleDetail.getStatus())){
                //保存转让中数据
                creditorSaleDetailListSelling.add(creditorSaleDetail);
                try {
                    //取消债权转让(只调用，不关注处理结果)
                    creditorSaleOrderCancelService.cancelCreditorSaleOrder(creditorSaleDetail.getId(), creditorSaleDetail.getUserId(), SaleDetailEndTypeEnum.REFUND);
                } catch (Exception e) {
                    log.info("债权订单取消异常，标的{},债权订单号{}",transFreezeRequestDTO.getProjectId(),creditorSaleDetail.getId());
                }
            }
        }
        //无处理中数据
        if (CollectionUtils.isEmpty(creditorSaleDetailListSelling)){
            log.info("标的债权无转让中，可以还款,projectId = {}",transFreezeRequestDTO.getProjectId());
            //刷新过期时间
            //transFreezeRequestDTO.setExpireTime(new DateTime().plusMinutes(StaticConstant.REFUND_FREZEE_EXPIRE_MINUTES).toDate());
            rabbitTemplate.convertAndSend(MQConstant.REFUND_FREEZE_EXCHANGE, MQConstant.REFUND_FREEZE_ROUTING, transFreezeRequestDTO);
        }else {
            log.info("标的债权转让中，还款挂起,projectId={}",transFreezeRequestDTO.getProjectId());
            throw new BizException("标的债权转让中，还款挂起");
        }
    }

    /**
     * 还款参数
     */
    @Data
    @AllArgsConstructor
    private static class RefundParam{
        private Project project;
        private OrderTradeSourceEnum orderTradeSourceEnum;
        private RefundTypeEnum refundTypeEnum;
        private RefundTradeTypeEnum refundTradeTypeEnum;
        private Date currentDate;
        private Long sourceAccountId;
        private String sourcePlatformUserNo;
        private String sourceUserId;
        private RefundOptTypeEnum refundOptTypeEnum;
        private Date compareDate;
        private List<Integer> periods;
        private String orgRequestNo;
        private BigDecimal availableAmount;

        public RefundParam(Project project, OrderTradeSourceEnum orderTradeSourceEnum, RefundTypeEnum refundTypeEnum,
                           RefundTradeTypeEnum refundTradeTypeEnum, Date currentDate, Long sourceAccountId,
                           String sourcePlatformUserNo, String sourceUserId, RefundOptTypeEnum refundOptTypeEnum,
                           Date compareDate, List<Integer> periods, String orgRequestNo){
            this(project, orderTradeSourceEnum, refundTypeEnum,
                    refundTradeTypeEnum, currentDate, sourceAccountId,
                    sourcePlatformUserNo, sourceUserId, refundOptTypeEnum,
                    compareDate, periods, orgRequestNo, null);
        }

    }

    /**
     * 发起还款异常消息通知
     * 
     * @param refundParam
     * @param failMessage
     */
    public void refundExceptionNotify(RefundParam refundParam, String failMessage){
    	RefundNotifyMsgDTO refundNotifyMsgDTO = new RefundNotifyMsgDTO();
    	refundNotifyMsgDTO.setRefundStatus(RefundNotifyRefundStatusEnum.REPAPYMENT_FAIL.getStatus());
		refundNotifyMsgDTO.setProjectId(refundParam.getProject().getProjectId());
		refundNotifyMsgDTO.setOrgRequestNo(refundParam.getOrgRequestNo());
		refundNotifyMsgDTO.setRefundTradeSource(refundParam.getOrderTradeSourceEnum().getTradeSource());
		refundNotifyMsgDTO.setFailMessage(failMessage);
		rabbitTemplate.convertAndSend(TradeMqConstant.REFUND_NOTIFY_HANDLE_EXCHANGE,
				TradeMqConstant.REFUND_NOTIFY_HANDLE_ROUTINGKEY, refundNotifyMsgDTO);
    }    
   
    /**
     * 标的逾期处理
     * 
     * 目前标的逾期统一在00：15非全额划扣JOB处理
     * 
     * @param summary
     * 					标的摘要
     * @param refundParam
     * 					还款参数
     * @param failMessage
     * 					异常信息
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
	public void projectOverdueHandle(ProjectBorrowerSummary summary, RefundParam refundParam, String failMessage) {
    	log.info("非全额划扣触发标的逾期处理，ProjectId:{}", summary.getProjectId());
    	
    	// 根据还款摘要还款状态获取标的变更前标的状态
    	int fromSubStatus = ProjectSubStatusEnum.REPAYING_INIT.getSubStatus();
		if (RepayStatusEnum.OVERDUE.getCode().equals(summary.getRepayStatus())) {
			fromSubStatus = ProjectSubStatusEnum.REPAYING_OVERDUE.getSubStatus();
		} else if (RepayStatusEnum.BADLOANS.getCode().equals(summary.getRepayStatus())) {
			fromSubStatus = ProjectSubStatusEnum.REPAYING_BAD.getSubStatus();
		}
		
    	// 标的还款中变更还款逾期需要更新标的相关状态
    	if (RepayStatusEnum.NORMAL.getCode().equals(summary.getRepayStatus())) {
    		updateProject2Overdue(summary);
    	}
    	
        // 标的逾期通知
		RefundNotifyMsgDTO refundNotifyMsgDTO = new RefundNotifyMsgDTO();
		if (StringUtils.hasLength(failMessage)) {
			refundNotifyMsgDTO.setRefundStatus(RefundNotifyRefundStatusEnum.REPAPYMENT_FAIL.getStatus());
		} else {
			refundNotifyMsgDTO.setRefundStatus(RefundNotifyRefundStatusEnum.REPAYMENT_INIT.getStatus());
		}
		refundNotifyMsgDTO.setProjectId(refundParam.getProject().getProjectId());
		refundNotifyMsgDTO.setOrgRequestNo(refundParam.getOrgRequestNo());
		refundNotifyMsgDTO.setRefundTradeSource(refundParam.getOrderTradeSourceEnum().getTradeSource());
		refundNotifyMsgDTO.setCurrentPeriod(summary.getCurrentPeriod());
		refundNotifyMsgDTO.setFromSubStatus(fromSubStatus);
		refundNotifyMsgDTO.setFailMessage(failMessage);
		rabbitTemplate.convertAndSend(TradeMqConstant.REFUND_NOTIFY_HANDLE_EXCHANGE,
				TradeMqConstant.REFUND_NOTIFY_HANDLE_ROUTINGKEY, refundNotifyMsgDTO);
		
		log.info("非全额划扣触发处理完成，ProjectId:{}", summary.getProjectId());
    }

    /**
     * 标的逾期，更新投资订单 回款中--》逾期回款中
     *
     * @param summary
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void fixOverdueForInvestOrders(ProjectBorrowerSummary summary, RefundParam refundParam) {
        // 只有逾期和坏账才有需要
        if (RepayStatusEnum.OVERDUE.getCode().equals(summary.getRepayStatus())
                || RepayStatusEnum.BADLOANS.getCode().equals(summary.getRepayStatus())){
            //已逾期的回款计划
            List<ProjectInvestorPlan> overdueInvestPlans = projectInvestorPlanMapperExt.listProjectPeriodInvestorPlanReceiveDate(summary.getProjectId(),refundParam.getCurrentDate());
            if (CollectionUtils.isNotEmpty(overdueInvestPlans)){
                Set<Long> investOrderIdSet = new HashSet<>();
                overdueInvestPlans.forEach(p -> investOrderIdSet.add(p.getInvestOrderId()));
                List<ProjectInvestOrder>  projectInvestOrderList = projectInvestOrderMapperExt.selectByInvestOrderIds(investOrderIdSet);

                log.info("fixOverdueForInvestOrders.start,project={},projectInvestOrderList.size={}",summary.getProjectId(),projectInvestOrderList.size());
                for (ProjectInvestOrder projectInvestOrder : projectInvestOrderList) {
                    if (ProjectInvestOrderEvolutionStatusEnum.BACKING.getValue().equals(projectInvestOrder.getEvolutionStatus())){
                        log.info("订单逾期，补充更新订单状态,investOrderId={}",projectInvestOrder.getId());
                        ProjectInvestOrder projectInvestOrderUpdater = new ProjectInvestOrder();
                        projectInvestOrderUpdater.setId(projectInvestOrder.getId());
                        projectInvestOrderUpdater.setEvolutionStatus(ProjectInvestOrderEvolutionStatusEnum.BACKING_OVERDUE.getValue());
                        projectInvestOrderMapperExt.updateByPrimaryKeySelective(projectInvestOrderUpdater);

                        ProjectPackageInvestOrderDetail detail = projectPackageInvestOrderDetailMapperExt.selectByInvestOrderId(projectInvestOrder.getId());
                        if (detail != null){
                            log.info("更新投资包数据 ProjectPackageInvestOrderDetail.id={}",detail.getId());
                            ProjectPackageInvestOrder projectPackageInvestOrder = projectPackageInvestOrderMapperExt.selectByPrimaryKey(detail.getPackageInvestOrderId());
                            if (projectPackageInvestOrder.getEvolutionStatus().equals(PackageInvestOrderEvolutionStatusEnum.BACKING.getValue())){
                                log.info("更新投资包数据 projectPackageInvestOrder状态为逾期,id={}",projectPackageInvestOrder.getId());
                                ProjectPackageInvestOrder projectPackageInvestOrderUpdater = new ProjectPackageInvestOrder();
                                projectPackageInvestOrderUpdater.setId(projectPackageInvestOrder.getId());
                                projectPackageInvestOrderUpdater.setEvolutionStatus(PackageInvestOrderEvolutionStatusEnum.BACKING_OVERDUE.getValue());
                                projectPackageInvestOrderMapperExt.updateByPrimaryKeySelective(projectPackageInvestOrderUpdater);
                            }
                        }
                    }
                }
                List<InvestorCreditor> investorCreditors = investorCreditorMapperExt.selectByInvestOrderIds(investOrderIdSet);
                for (InvestorCreditor investorCreditor : investorCreditors) {
                    if (InvestorCreditorStatusEnum.NORMAL_RECEIVING.getStatus().equals(investorCreditor.getStatus())){
                        InvestorCreditor projectInvestorCreditorUpdater = new InvestorCreditor();
                        projectInvestorCreditorUpdater.setId(investorCreditor.getId());
                        projectInvestorCreditorUpdater.setStatus(InvestorCreditorStatusEnum.RECEIVE_OVERDUE.getStatus());
                        investorCreditorMapperExt.updateByPrimaryKeySelective(projectInvestorCreditorUpdater);
                    }
                }
            }
        }


    }

    /**
     * 更新项目状态到逾期
     * 
     * @param summary
     */
    private void updateProject2Overdue(ProjectBorrowerSummary summary) {
        Long projectId = summary.getProjectId();
        Project projectUpdater = new Project();
        projectUpdater.setProjectId(projectId);
        projectUpdater.setSubStatus(ProjectSubStatusEnum.REPAYING_OVERDUE.getSubStatus());
        projectUpdater.setUpdateTime(new Date());
        projectMapperExt.updateByPrimaryKeySelective(projectUpdater);

        ProjectBorrowerSummary summaryUpdater = new ProjectBorrowerSummary();
        summaryUpdater.setProjectId(projectId);
        summaryUpdater.setRepayStatus(RepayStatusEnum.OVERDUE.getCode());
        summaryUpdater.setUpdateTime(new Date());
        projectBorrowerSummaryMapperExt.updateByPrimaryKeySelective(summaryUpdater);

        //处理投资订单处理
        List<ProjectInvestOrder> projectInvestOrders = projectInvestOrderMapperExt.selectInvestedList(projectId);
        for (ProjectInvestOrder projectInvestOrder : projectInvestOrders) {
            if (ProjectInvestOrderEvolutionStatusEnum.BACKING.getValue().equals(projectInvestOrder.getEvolutionStatus())){
                ProjectInvestOrder projectInvestOrderUpdater = new ProjectInvestOrder();
                projectInvestOrderUpdater.setId(projectInvestOrder.getId());
                projectInvestOrderUpdater.setEvolutionStatus(ProjectInvestOrderEvolutionStatusEnum.BACKING_OVERDUE.getValue());
                projectInvestOrderMapperExt.updateByPrimaryKeySelective(projectInvestOrderUpdater);

                ProjectPackageInvestOrderDetail detail = projectPackageInvestOrderDetailMapperExt.selectByInvestOrderId(projectInvestOrder.getId());
                if (detail != null){
                    log.info("更新投资包数据 ProjectPackageInvestOrderDetail.id={}",detail.getId());
                    ProjectPackageInvestOrder projectPackageInvestOrder = projectPackageInvestOrderMapperExt.selectByPrimaryKey(detail.getPackageInvestOrderId());
                    if (projectPackageInvestOrder.getEvolutionStatus().equals(PackageInvestOrderEvolutionStatusEnum.BACKING.getValue())){
                        log.info("更新投资包数据 projectPackageInvestOrder状态为逾期,id={}",projectPackageInvestOrder.getId());
                        ProjectPackageInvestOrder projectPackageInvestOrderUpdater = new ProjectPackageInvestOrder();
                        projectPackageInvestOrderUpdater.setId(projectPackageInvestOrder.getId());
                        projectPackageInvestOrderUpdater.setEvolutionStatus(PackageInvestOrderEvolutionStatusEnum.BACKING_OVERDUE.getValue());
                        projectPackageInvestOrderMapperExt.updateByPrimaryKeySelective(projectPackageInvestOrderUpdater);
                    }
                }

            }
        }

        List<InvestorCreditor> investorCreditors = investorCreditorMapperExt.selectByInvestOrderIdsByProjectId(projectId);
        for (InvestorCreditor investorCreditor : investorCreditors) {
            if (InvestorCreditorStatusEnum.NORMAL_RECEIVING.getStatus().equals(investorCreditor.getStatus())){
                InvestorCreditor projectInvestorCreditorUpdater = new InvestorCreditor();
                projectInvestorCreditorUpdater.setId(investorCreditor.getId());
                projectInvestorCreditorUpdater.setStatus(InvestorCreditorStatusEnum.RECEIVE_OVERDUE.getStatus());
                investorCreditorMapperExt.updateByPrimaryKeySelective(projectInvestorCreditorUpdater);
            }
        }

    }

    /**
     * 校验规则：
     1、判断指定期数是否是连续的
     2、判断指定期数是否已经有还款
     3、判断指定期数是否大于总期数
     4、判断指定期数是否可以还款
     * @param projectId
     * @param periods
     * @return
     */
    public Boolean checkPeriodsIsLegal(Long projectId,List<Integer> periods){
        //1、判断指定期数是否是连续的
        if(periods==null){
            return false;
        }
        Collections.sort(periods);
        Integer maxPeriod = periods.get(periods.size()-1);
        Integer minPeriod = periods.get(0);
        if(maxPeriod-minPeriod+1!=periods.size()){
            return  false;
        }
        //2、判断指定期数是否已经有还款
        Integer count = projectBorrowerPlanNewMapperExt.countReceivedNumberByProjectIdAndPeriods(projectId, periods);
        if(count>0){
            return false;
        }
        ProjectBorrowerSummary summary = projectBorrowerSummaryMapperExt.selectByPrimaryKey(projectId);
        Integer toRepayPeriod = refundCommonService.getCurrentToRepayPeriod(summary);
        for(Integer period : periods){
            //3、判断指定期数是否大于总期数
            if(period>summary.getTotalPeriod()){
                return false;
            }
            //4、判断指定期数是否可以还款
            if(period>toRepayPeriod){
                return false;
            }
        }
        return true;
    }
}
